import os
import subprocess
import tempfile
import threading

class StreamReader(threading.Thread):
    
    def __init__(self, name, in_file):
        threading.Thread.__init__(self, name='readthread-%s' % name)
        self._in_file = in_file
        self._lines = []
        
    def run(self):
        
        for line in self._in_file:
            self._lines.append(line)
            print "Mencoder ERR: %s" % line
            
    # not thread safe
    def get_lines(self):
        return self._lines
            
class MencoderError(Exception):
    
    def __init__(self, msg=None):
        Exception.__init__(self, msg)

class UnrecognisedFormatError(MencoderError):
    
    def __init__(self, filename):
        MencoderError.__init__(self, "%s is not recognised as a video file by mencoder" % filename)

class MencoderOutputReader(threading.Thread):
    
    def __init__(self, name, in_file):
        threading.Thread.__init__(self, name='readthread-%s' % name)
        self._in_file = in_file
        
    def set_progress_callback(self, callback):
        self._prog_callback = callback
        
    def run(self):
        
        print 'MencoderOutputReader: reading starting %s' % self.getName()
        
        LBRACK = '('
        RBRACK = ')'
        token_buf = []
        seen_pos = False
        seen_lbrack = False
        while True:
            #line = self._in_file.readline()
            char = self._in_file.read(1)
            
            if char == '':
                break
            else:
                if char == LBRACK:
                    seen_lbrack = True
                elif seen_lbrack:
                    
                    if char == RBRACK:
                        token = ''.join(token_buf)
                        token_buf = []
                        seen_lbrack = False
                        try:
                            progress = self.parse_progress(token)
                            if self._prog_callback:
                                self._prog_callback(progress)
                        except EncoderStreamError, (instance):
                            print 'Error reading progress on token %s: %s' % (token, instance)
                    else:
                        token_buf.append(char)    
                
        print 'finished reading thread for %s' % self.getName()
        
    def parse_progress(self, token):
        
        eidx = token.find('%')
        try:
            return int(token[0:eidx])
        except ValueError:
            raise EncoderStreamError('unable to read progress %s' % token)

class EncoderStreamError(Exception):
    def __init__(self, bad_token):
        self._bad_token = bad_token
        
    def __str__(self):
        return repr(self._bad_token)


class MencoderWatcher(threading.Thread):
    
    def __init__(self, process):
        threading.Thread.__init__(self, name='ProcessWatcher')
        self._process = process
    def run(self):
        print 'waiting for process %s' % self.getName()
        self._process.wait()
        print 'process finished'
    

class Encoder():
    
    def __init__(self):
        self._out_bitrate = 400
        self._observers = []
        pass
    
    def set_input_filename(self, filename):
        self._input_filename = filename
        
    def set_output_bitrate(self, bitrate):
        pass
        
    def set_output_dimensions(self, width, height):
        self._out_width = width
        self._out_height = height
        
    def add_output_observer(self, observer):
        self._observers.append(observer)
        
    def encode(self):
        self._t_file = t_file = tempfile.NamedTemporaryFile('w+b', -1)
        encoding_args = self._build_mencoder_args(t_file.name)
        proc_args = ['mencoder', self._input_filename]
        proc_args.extend(encoding_args)
        print 'Encoder: %s' % proc_args
        
        try:
            process = self.process = subprocess.Popen(
                                       proc_args,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE
                                       )
        except OSError, (instance):
            print 'Encoder: error starting process %s' % instance
            raise MencoderError(msg='Unable to start mencoder: %s' % str(instance))
                
        
        print 'Encoder: mencoder process started'
        
        self.out_thread = MencoderOutputReader('stdout', process.stdout)
        
        def callback(progress):
            for observer in self._observers:
                observer.update_progress(progress)
        
        self.out_thread.set_progress_callback(callback)
        self.err_thread = StreamReader('stderr', process.stderr)
        
        # read response from mencoder
        print 'Encoder: starting output reader'
        self.out_thread.start()
        print 'Encoder: starting error reader'
        self.err_thread.start()
        
        process.wait()
        
        err_string = '\n'.join(self.err_thread.get_lines())
        
        if process.returncode != 0:
            if err_string.find("Sorry, this file format is not recognized/supported") != -1:
                raise UnrecognisedFormatError(self._input_filename)
            else:
                raise MencoderError('Encoding Failed: %s' % err_string)
        
    def get_outputfile(self):
        return self._t_file.name
        
    def _build_mencoder_args(self, tempfile):
        args = []
        
        # mencoder $1 -ovc lavc -lavcopts 
        # vcodec=mpeg4:vbitrate=512:vhq:keyint=250:threads=2 
        # -oac mp3lame -lameopts cbr:br=64 -ffourcc XVID 
        # -vf scale=320:-2,crop=320:240,expand=320:240 -af resample=44100:0:0 -o $2

        
        # xvid backend
        args.append('-ovc')
        args.append('lavc')
        args.append('-lavcopts')
        args.append('vcodec=mpeg4:vbitrate=%s:vhq:keyint=250:threads=2' % self._out_bitrate)
        args.append('-ffourcc')
        args.append('XVID')

        # audio to mp3
        args.append('-oac')
        args.append('mp3lame')
        args.append('-lameopts')
        args.append('cbr:br=128')
        
        # rescale
        args.append('-vf')
        w = self._out_width
        h = self._out_height
        scale_tuple = (w, w, h, w, h)
        args.append('scale=%s:-2,crop=%s:%s,expand=%s:%s' % scale_tuple)
        args.append('-af')
        args.append('resample=44100:0:0')
        
        # temporary file needed
        args.append('-o')
        args.append('%s' % tempfile)
        
        return args
        